home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-09-22 | 9.7 KB | 328 lines | [TEXT/CWIE] |
- //
- //
- // File: ComputeLength.c
- //
- // Joseph Maurer, April 1996
- //
- //
-
-
-
- #include <stdio.h>
- #include <Types.h>
- #include <fp.h>
-
- #include "ComputeLength.h"
-
- /* better than 1/2 pixel */
- #define kEpsilon 0.1
-
- //-----------------------------------------------------------------
- struct CurveCoefficients {
- double a0, a1, a2; // x(t) = a0 + t * (a1 + t * a2);
- double b0, b1, b2; // y(t) = b0 + t * (b1 + t * b2);
- Boolean isLinear; // true if all three points are colinear.
- };
- typedef struct CurveCoefficients CurveCoefficients, *CurveCoeffPtr;
-
-
- //------------------------------------------------------------------
- // A (16.16) Fixed number is a 32-bit long integer "n" representing
- // a decimal value of (n / 65536).
- #define kFloatToFixed 65536.0
-
- #define MULTIPLY_DOUBLE_WITH_FIXED(d, n) (Fixed)((d) * (double)(n))
- //------------------------------------------------------------------
- void CurveToLine(gxCurve *theCurve, gxLine *theLine);
- void CurveToLine(gxCurve *theCurve, gxLine *theLine)
- {
- // function takes a curve that is really a line and makes a line structure out of
- // it. It checks the 3 combinations of the three points to find the two furthest apart
- // because it is not necessarily the first and last points.
- // use doubles to avoid overflow. Function assumes the curve was all colinear points.
- double x1, y1, x2, y2, x3, y3;
- double d1, d2, d3;
-
- x1 = theCurve->first.x / kFloatToFixed;
- y1 = theCurve->first.y / kFloatToFixed;
- x2 = theCurve->control.x / kFloatToFixed;
- y2 = theCurve->control.y / kFloatToFixed;
- x3 = theCurve->last.x / kFloatToFixed;
- y3 = theCurve->last.y / kFloatToFixed;
-
- d1 = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1); // line: first-control
- d2 = (x3-x1)*(x3-x1) + (y3-y1)*(y3-y1); // line: first-last
- d3 = (x3-x2)*(x3-x2) + (y3-y2)*(y3-y2); // line: control-last
-
- if ( (d1 > d2) && (d1 > d3) ) { // d1 longest
-
- theLine->first.x = theCurve->first.x;
- theLine->first.y = theCurve->first.y;
- theLine->last.x = theCurve->control.x;
- theLine->last.y = theCurve->control.y;
-
- } else if ( (d2 > d1) && (d2 > d3) ) { // d2 longest
-
- theLine->first.x = theCurve->first.x;
- theLine->first.y = theCurve->first.y;
- theLine->last.x = theCurve->last.x;
- theLine->last.y = theCurve->last.y;
-
- } else { // d3 must be longest
-
- theLine->first.x = theCurve->control.x;
- theLine->first.y = theCurve->control.y;
- theLine->last.x = theCurve->last.x;
- theLine->last.y = theCurve->last.y;
-
- }//end if
-
- }
-
- //------------------------------------------------------------------
- // Can you say "Pythagore"?
- // We convert to floating point for more room to breathe with the
- // squares, and for the convenience of sqrt().
- double GetLineLength(gxLine *theLine)
- {
- double a = (theLine->last.x - theLine->first.x) / kFloatToFixed;
- double b = (theLine->last.y - theLine->first.y) / kFloatToFixed;
- return sqrt(a * a + b * b);
- }
-
-
- //------------------------------------------------------------------
- // "theLength" can be any Fixed number in the range 0x80000000 ... 0x7FFFFFFF
- // "theTangent" always is the vector defined by theLine->first ... theLine->last.
- void LineLengthToPoint(double theLength, gxLine *theLine, gxPoint *thePoint, gxPoint *theTangent)
- {
- double totallength = GetLineLength(theLine);
- double t;
- Fixed dx, dy;
-
- if (totallength != 0.0)
- t = theLength / totallength;
- else
- t = 0.0;
-
- dx = theLine->last.x - theLine->first.x;
- dy = theLine->last.y - theLine->first.y;
-
- if (thePoint) {
- thePoint->x = theLine->first.x + MULTIPLY_DOUBLE_WITH_FIXED(t, dx);
- thePoint->y = theLine->first.y + MULTIPLY_DOUBLE_WITH_FIXED(t, dy);
- }
-
- /* now normalize the tangent */
-
- if (theTangent && (totallength != 0.0)) {
- theTangent->x = FixedDivide( dx, (Fixed)(totallength * kFloatToFixed));
- theTangent->y = FixedDivide( dy, (Fixed)(totallength * kFloatToFixed));
- }//end if
- }
-
-
- //------------------------------------------------------------------
- // The conditions (x(0), y(0)) = theCurve->first, (x(1), y(1)) = theCurve->last,
- // (x'(0), y'(0)) = theCurve->control - theCurve->first (tangent at "first")
- // (x'(1), y'(1)) = theCurve->last - theCurve->control (tangent at "last")
- // are sufficient to determine the coefficients of the quadratic parametrization.
- static void ComputeCurveCoefficients(gxCurve *theCurve, CurveCoefficients* cc)
- {
- double fixedToFloat = 1 / kFloatToFixed;
-
- cc->a0 = fixedToFloat * theCurve->first.x;
- cc->a1 = fixedToFloat * ((theCurve->control.x - theCurve->first.x) << 1);
- cc->a2 = fixedToFloat * (theCurve->last.x - (theCurve->control.x << 1) + theCurve->first.x);
-
- cc->b0 = fixedToFloat * theCurve->first.y;
- cc->b1 = fixedToFloat * ((theCurve->control.y - theCurve->first.y) << 1);
- cc->b2 = fixedToFloat * (theCurve->last.y - (theCurve->control.y << 1) + theCurve->first.y);
-
- cc->isLinear = ( cc->a2 == 0.0 ) && (cc->b2 == 0.0) ? true : false;
- }
-
-
- //------------------------------------------------------------------
- // That's the integrand sqrt(x'(t)^2 + y'(t)^2) (if you look closely)
- static double LengthFunction(CurveCoefficients* cc, double t)
- {
- double A = 4 * ((cc->a2 * cc->a2) + (cc->b2 * cc->b2));
- double B = 4 * ((cc->a1 * cc->a2) + (cc->b1 * cc->b2));
- double C = (cc->a1 * cc->a1) + (cc->b1 * cc->b1);
-
- return sqrt(C + t * (B + t * A));
- }
-
-
- //------------------------------------------------------------------
- // After all, this is the most appropriate solution:
- // Use the closed formula for the length integral!
- // (Mathematica did it for me ...)
- static double EvaluateClosedFormula(CurveCoefficients* cc, double x)
- {
- double A, B, C, SC, SCBX, result;
-
- A = 4 * ((cc->a2 * cc->a2) + (cc->b2 * cc->b2)); // should always be > 0
- if (A == 0.0)
- {
- DebugStr("\pCurve not quadratic");
- return 0;
- }
- B = 4 * ((cc->a1 * cc->a2) + (cc->b1 * cc->b2)) / A;
- C = ((cc->a1 * cc->a1) + (cc->b1 * cc->b1)) / A;
- SC = sqrt(C);
- SCBX = sqrt(C + x * (B + x));
- result = 4.0 * x * SCBX - 2.0 * B * (SC - SCBX);
- result = result + (B * B - 4.0 * C) * (log(0.5 * B + SC) - log(0.5 * B + x + SCBX));
- result = sqrt(A) * result / 8.0;
- return result;
- }
-
-
- //------------------------------------------------------------------
- static double NewLengthToParameterFunction(CurveCoefficients* cc, double s)
- {
- double a = 0.0;
- double b = 1.0;
- double c, d;
-
- // could set error if s is "out of range"
-
- if (s <= 0.0)
- return 0.0;
- if (s >= EvaluateClosedFormula(cc, 1.0))
- return 1.0;
-
- do {
- c = 0.5 * (a + b);
- d = s - EvaluateClosedFormula(cc, c);
- if (d < 0) // c too far right
- b = c;
- else // c too far left
- a = c;
- }
- while (fabs(d) > kEpsilon);
-
- return c;
- }
-
-
-
- //------------------------------------------------------------------
- double GetCurveLength(gxCurve *theCurve)
- {
- CurveCoefficients cc;
- double result;
-
- ComputeCurveCoefficients(theCurve, &cc);
-
- if (cc.isLinear) {
-
- gxLine theLine;
- CurveToLine(theCurve, &theLine);
- result = GetLineLength(&theLine);
-
- } else {
-
- result = EvaluateClosedFormula(&cc, 1.0);
-
- }//end if
-
- return result;
- }
-
-
- //------------------------------------------------------------------
- static void EvaluateCurveParametrization(CurveCoefficients* cc, double t, gxPoint* thePoint)
- {
- if (thePoint) {
- thePoint->x = (Fixed) (kFloatToFixed * (cc->a0 + t * (cc->a1 + t * (cc->a2))));
- thePoint->y = (Fixed) (kFloatToFixed * (cc->b0 + t * (cc->b1 + t * (cc->b2))));
- }//end if
- }
-
- //------------------------------------------------------------------
- void CurveLengthToPoint(double theLength, gxCurve *theCurve, gxPoint *thePoint, gxPoint *theTangent)
- {
- CurveCoefficients cc;
- double tx, ty, norm;
- double t;
-
- ComputeCurveCoefficients(theCurve, &cc);
-
- if (cc.isLinear) {
-
- gxLine theLine;
- CurveToLine(theCurve, &theLine);
-
- LineLengthToPoint(theLength, &theLine, thePoint, theTangent);
-
- } else {
-
- t = NewLengthToParameterFunction(&cc, theLength);
- EvaluateCurveParametrization(&cc, t, thePoint);
- tx = cc.a1 + t * 2.0 * (cc.a2); // that's the derivative
- ty = cc.b1 + t * 2.0 * (cc.b2);
- norm = sqrt(tx * tx + ty * ty);
- if (theTangent && (norm > 0)) {
- theTangent->x = (Fixed) (kFloatToFixed * tx / norm);
- theTangent->y = (Fixed) (kFloatToFixed * ty / norm);
- }
-
- }//end if
- }
-
-
- //------------------------------------------------------------------
- void DetermineSubLine(gxLine *theLine, double a, double b, gxLine *subLine)
- {
-
- LineLengthToPoint(a, theLine, &subLine->first, nil);
- LineLengthToPoint(b, theLine, &subLine->last, nil);
-
- }
- //------------------------------------------------------------------
- void DetermineSubCurve(gxCurve *theCurve, double a, double b, gxCurve *subCurve)
- {
- CurveCoefficients cc;
- double t0, t1;
- gxPoint midPoint;
-
- ComputeCurveCoefficients(theCurve, &cc);
-
- if (cc.isLinear) {
-
- /* Treat the curve as a line if it was linear */
- gxLine theLine, subLine;
-
- CurveToLine(theCurve, &theLine);
-
- DetermineSubLine(&theLine, a, b, &subLine);
-
- subCurve->first.x = subLine.first.x;
- subCurve->first.y = subLine.first.y;
- subCurve->control.x = (subLine.first.x + subLine.last.x) / 2; // make it the midpoint
- subCurve->control.y = (subLine.first.y + subLine.last.y) / 2;
- subCurve->last.x = subLine.last.x;
- subCurve->last.y = subLine.last.y;
-
- } else {
-
- /* compute the sub curve */
-
- t0 = NewLengthToParameterFunction(&cc, a);
- t1 = NewLengthToParameterFunction(&cc, b);
- EvaluateCurveParametrization(&cc, t0, &subCurve->first);
- EvaluateCurveParametrization(&cc, t1, &subCurve->last);
- EvaluateCurveParametrization(&cc, 0.5 * (t0 + t1), &midPoint);
- // midPt = (start + end) / 4 + control / 2 ; control = midPt * 2 - (start + end) / 2
- // note that we choose to ignore arithmetic overflow, here:
- // usable coordinate range limited to -0x3FFF ... 0x3FFF !
- subCurve->control.x = (midPoint.x << 1) - ((subCurve->first.x + subCurve->last.x) >> 1);
- subCurve->control.y = (midPoint.y << 1) - ((subCurve->first.y + subCurve->last.y) >> 1);
-
- }//end if
- }
-
-